import java.io.*;
import java.util.*;

/** @author Chris Howson & Steven Horsman */
class Sudoku
{
    /**Main function - Provides the interactive mode*/
    public static void main(String args[])
    {
        Sudoku program = new Sudoku();
        if(args.length != 0)
        {
            System.err.println("Too many input arguements");
        }
        else
        {
            BufferedReader standard = new BufferedReader(new InputStreamReader(System.in));
            Matrix a = null;
            try
            {
                program.output("ln","Instructions:\n-Load Matrix: load \"filename\"\n-Solve Puzzle: solve\n-Clue: clue\n-Check Solution: check\n-Print Current Progress: print\n-Guess: guess \"row\", \"col\", \"data\"");
                String inline = "";
                System.out.print(">>");
                inline = standard.readLine();
                inline.toLowerCase();
                while(!inline.equals("exit"))
                {
                    try
                    {
                        String[] array = inline.split(" ");
                        if(array[0].equals("load") && array.length == 2)
                        {
                            a = program.load_puzzle(array[1]);
                            program.print_sudoku(a);
                            program.output("ln","Loaded " + array[1]);
                        }
                        else if(array[0].equals("guess") && array.length ==4)
                        {
                        	int col = array[2].charAt(0) - 65;
                            a.setCell(Integer.parseInt(array[1])-1,col, Double.parseDouble(array[3]));
                            program.output("ln","Guessed (" + array[1] + "," + array[2] + ") as " + array[3]);
                        }
                        else if(array[0].equals("solve"))
                        {
                        	Calendar start = new GregorianCalendar();
                            program.solver(a,"solve");
                            Calendar end = new GregorianCalendar();
                            long diffMillis = end.getTimeInMillis()-start.getTimeInMillis();
                            System.out.println("Time = " + (double)(diffMillis/1000.00) + " seconds.");
                        }
                        else if(array[0].equals("clue"))
                        {
                            program.solver(a,"clue");
                        }
                        else if(array[0].equals("check"))
                        {
                            program.sudoku_checker(a,true);
                        }
                        else if(array[0].equals("print"))
                        {
                            program.print_sudoku(a);
                        }
                        else
                        {
                            throw new Exception();
                        }
                    }
                    catch(Exception e)
                    {
                        System.err.println("Error with Input, try again - " + e);
                    }
                    program.output("notln",">>");
                    inline = standard.readLine();
                    inline.toLowerCase();
                }
            }
            catch (Exception e)
            {
                System.err.println("ERROR: " + e.getMessage());
            }
        }
    }
    
    /**Prints to standard output the given string*/
    void output(String ln,String outputtext)
    {
    	if(ln.equals("ln")) { System.out.println(""+outputtext); }
    	else { System.out.print(""+outputtext); }
    }
    
    /**Loads a puzzle into a matrix from a txt file*/
    Matrix load_puzzle(String filename)
    {
        Matrix s = new MatrixArray(9,9);
        s = s.load_Matrix(filename);
        return s;
    }
    
    /**Prints a puzzle with formatting to make it easier to see*/
    void print_sudoku(Matrix input)
    {
    	System.out.println("  | A B C | D E F | G H I |");
        System.out.println("  |-------|-------|-------|");
        for(int i = 0; i < input.getRows(); i++)
        {
        	System.out.print((i+1) + " ");
        	for(int j = 0; j < input.getCols(); j++)
            {
                if(j%3 == 0) { System.out.print("| "); }
                System.out.print((int)input.getCell(i,j) + " ");
            }
            System.out.println("|");
            if((i+1)%3 == 0) { System.out.println("  |-------|-------|-------|"); }
        }
    }
    
    /**Checks through a solution to see whether it is correct*/
    Boolean sudoku_checker(Matrix input, Boolean flag)
    {
        int[] checker = new int[9];
        int[] checker_row = new int[9];
        int[] checker_col = new int[9];
        boolean correct = true;
        boolean incomplete = false;
        checker = resetarray(checker);
        int errors = 0;
        int togo = 0;
        for(int i = 0; i < 9; i++)
        {
            //Check grid
            if(correct)
            {
                Matrix a = getGrid(input, i+1);
                Matrix b = getRow(input, i+1);
                Matrix c = getCol(input, i+1);
                checker = resetarray(checker);
                checker_row = resetarray(checker_row);
                checker_col = resetarray(checker_col);
                
                for(int j = 0; j < 3; j++)
                {
                    for(int k = 0; k < 3; k++)
                    {
                        int temp = (int)a.getCell(j,k);
                        int temp_row = (int)b.getCell(0,(j*3)+k);
                        int temp_col = (int)c.getCell((j*3)+k,0);
                        if(temp == 0 || temp_row == 0 || temp_col == 0)
                        {
                        	incomplete = true;
                        }
                        else if(checker[temp-1] != 0 || checker_row[temp_row-1] != 0 || checker_col[temp_col-1] != 0)
                        {
                            correct = false;
                            errors++;
                        }
                        if(temp_row == 0) { togo++; }
                        if(temp != 0 && temp_row != 0 && temp_col != 0)
                        {
                        	checker[temp-1] = 1;
                        	checker_row[temp_row-1] = 1;
                        	checker_col[temp_col-1] = 1;
                        }
                    }
                }
            }
        }
        if(correct)
        {
        	if(incomplete)
        	{
        		if (flag) System.out.println("Correct Solution so far, " + togo + " to go");
        		return false;
        	}
        	else
        	{
        		if (flag) System.out.println("Correct Solution :-)");
        		return true;
        	}
        }
        else
        {
        	if (flag) System.out.println("Incorrect! " + errors + " errors found.");
        	return false;
        }
    }
    
    /**Solves a puzzle*/
    void solver(Matrix input, String a)
    {
    	SudokuSolver so = new SudokuSolver(input);
    	Boolean carryon = true;
    	Boolean flag = false;
    	SolverData[][] solv = solverintialiser(9,9);
    	while(carryon)
    	{
    		so.solverupdater(solv, input,flag);
    		carryon = so.solveradder(solv, input);
            if(a.equals("clue")) { carryon = false; }
            else if(!a.equals("clue") && !carryon)
            {
            	//check solution
            	if(!(sudoku_checker(so.getPuzzle(),false)))
            	{
            		flag = true;
            		//print_sudoku(so.getPuzzle());
            		carryon = so.guess(solv);
            		//print_sudoku(so.getPuzzle());
            	}
            }
    	}
    	print_sudoku(so.getPuzzle());
    	System.out.flush();
    }
    
    /**Calculate start row from input*/
    int workoutstartrow(int i)
    {
    	if(i < 3) { return 0; }
    	else if(i < 6) { return 3; }
    	else { return 6; }
    }

    /**Initialise solver array*/
    SolverData[][] solverintialiser(int rows, int cols)
    {
    	SolverData[][] solv = new SolverData[9][9];
    	for(int i = 0; i < 9; i++)
    	{
    		for(int j = 0; j < 9; j++)
    		{
    			solv[i][j] = new SolverData();
    			solv[i][j].setalltrue();
    		}
    	}
    	return solv;
    }
    
    /**Reset a int array to 0's*/
    int[] resetarray(int[] checker)
    {
        int[] newchecker = new int[9];
        for(int i = 0; i < 9; i++) { newchecker[i] = 0; }
        return newchecker;
    }
    
    /**Return a 3x3 grid of the puzzle*/
    Matrix getGrid(Matrix input, int no)
    {
        Matrix output = new MatrixArray(3,3);
        int startrow = workoutstartrow(no);
        int startcol = 0;
                
        if(no == 1 || no == 4 || no == 7) { startcol = 0; }
        else if(no == 2 || no == 5 || no == 8) { startcol = 3; }
        else { startcol = 6; }
        
        for(int i = 0; i < 3; i++)
        {
            for(int j = 0; j < 3; j++)
            {
                output.setCell(i,j, input.getCell((startrow+i), (startcol+j)));
            }
        }
        return output;
    }
    
    /**Return a row of the puzzle*/
    Matrix getRow(Matrix input, int no)
    {
        Matrix output = new MatrixArray(1,9);
        for(int i = 0; i < 9; i++)
        {
            output.setCell(0,i,input.getCell(no-1,i));
        }
        return output;
    }
    
    /**Return a column of the puzzle*/
    Matrix getCol(Matrix input, int no)
    {
        Matrix output = new MatrixArray(9,1);
        for(int i = 0; i < 9; i++)
        {
            output.setCell(i,0,input.getCell(i, no-1));
        }
        return output;
    }
}
